/******************************************************************************
* echo_server.c                                                               *
*                                                                             *
* Description: This file contains the C source code for an echo server.  The  *
*              server runs on a hard-coded port and simply write back anything*
*              sent to it by connected clients.  It does not support          *
*              concurrent clients.                                            *
*                                                                             *
* Authors: Athula Balachandran <abalacha@cs.cmu.edu>,                         *
*          Wolf Richter <wolf@cs.cmu.edu>                                     *
*                                                                             *
*******************************************************************************/

#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include "parse.h"
#include <unistd.h>
#include <sys/select.h>
#include <ctype.h>


#define ECHO_PORT 9999
#define BUF_SIZE 8192

char stversion[] = "HTTP/1.1";
char stURI[] = "./static_site";
char css[] = "text/css";
char png[] = "image/png";
char jpg[] = "image/jpeg";
char gif[] = "image/gif";
char html[] = "text/html";
char *hashtext[100];
char timebuffer[128];
char timebuffer_modified[128];

const char _get[] = {'G', 'E', 'T', '\0'};
const char _post[] = {'P', 'O', 'S', 'T', '\0'};
const char _head[] = {'H', 'E', 'A', 'D', '\0'};

int close_socket(int sock)
{
    if (close(sock))
    {
        fprintf(stderr, "Failed closing socket.\n");
        return 1;
    }
    return 0;
}

char* Int2String(int num,char *str)//10进制 
{
    int i = 0;//指示填充str 
    if(num<0)//如果num为负数，将num变正 
    {
        num = -num;
        str[i++] = '-';
    } 
    //转换 
    do
    {
        str[i++] = num%10+48;//取num最低位 字符0~9的ASCII码是48~57；简单来说数字0+48=48，ASCII码对应字符'0' 
        num /= 10;//去掉最低位    
    }while(num);//num不为0继续循环
    
    str[i] = '\0';
    
    //确定开始调整的位置 
    int j = 0;
    if(str[0]=='-')//如果有负号，负号不用调整 
    {
        j = 1;//从第二位开始调整 
        ++i;//由于有负号，所以交换的对称轴也要后移1位 
    }
    //对称交换 
    for(;j<i/2;j++)
    {
        //对称交换两端的值 其实就是省下中间变量交换a+b的值：a=a+b;b=a-b;a=a-b; 
        str[j] = str[j] + str[i-1-j];
        str[i-1-j] = str[j] - str[i-1-j];
        str[j] = str[j] - str[i-1-j];
    } 
    
    return str;//返回转换后的值 
}

/**************************************
  该方法包装了send()
  通过该方法发送数据 能够全部发出
  没有遗漏
**************************************/
int sendall(int s, char *buf, int *len) {
  int total = 0;                      // 已经发送字节数
  int bytesleft = *len;                                                                    //还剩余多少字节
  int n;
  while(total < *len) {
    n = send(s, buf+total, bytesleft, 0);
    if (n == -1) { break; }
    total += n;
    bytesleft -= n;
  }
  *len = total;                      // 返回实际发送出去的字节数
  return n==-1?-1:0;                    // 成功发送返回0 失败-1
}




int exist(char * filename)
{
    int fp = open(filename, O_RDONLY);
    if(fp < 0)
    {
      return 0;
    }
    else
    {
      close(fp);
      return 1;
    }
}

int splitext(char *URI)
{
    int len = strlen(URI);
    for (int j = len - 1; j >= 0; j--)
    {
        if (URI[j] == '.')
        {
            return j;
        }
    }
    return -1;
}

int split_wenhao(char * URI)
{
    int len = strlen(URI);
    for (int j = len - 1; j >= 0; j--)
    {
        if (URI[j] == '?')
        {
            return j;
        }
    }
    return -1;
}

void send2client(char *buf_send, int client_sock, int sock)
{
    if (send(client_sock, buf_send, strlen(buf_send), 0) != strlen(buf_send))
    {
        close_socket(client_sock);
        close_socket(sock);
        fprintf(stderr, "Error sending to client.\n");
        return EXIT_FAILURE;
    }
}

void getmycomputertime()
{
    time_t rawtime;
    struct tm *timeinfo;
    time(&rawtime);
    timeinfo = gmtime(&rawtime);
    strftime(timebuffer, sizeof(timebuffer), "%a, %d %b %Y %H:%M:%S GMT\r\n", timeinfo);
}

int check_http_version(char *http_version)
{

    if (strcasecmp(http_version, stversion) == 0)
    {
        return 1;
    }
    return 0;
}

void creathash(char *hashtext[])
{
    char pos = 'c';
    hashtext[pos] = css;
    pos = 'h';
    hashtext[pos] = html;
    pos = 'p';
    hashtext[pos] = png;
    pos = 'j';
    hashtext[pos] = jpg;
    pos = 'g';
    hashtext[pos] = gif;
}

void removeSubString(char *str, int index, int size)
{
    if (NULL == str || index < 0 || size < 0)
    {
        printf("addBigInt param errpr\n");
        return;
    }
    int len = strlen(str);
    char *p = NULL, *t = NULL;

    p = str + index;
    t = p + size;

    memcpy(p, t, len - index - size);
    str[len - size] = '\0';

    return;
}


void my_log (int a, int b, unsigned long ipaddr, char* requestaddr, int size){
    time_t curtime;
    struct tm* info;
    char buffer[80];//strftime函数
    time(&curtime); //时间
    info = localtime(&curtime);
    strftime(buffer,80,"%d/%b/%Y:%X", info);//访问日志时间格式：[10/Oct/2000:13:55:36 -0700]
    pid_t process_id;
    process_id = getpid();  //进程id
        
    pthread_t tid;
    tid = pthread_self();  //线程id
        
    FILE * fp;    //创建文档

    int addr_1 = ipaddr >> 24;
    ipaddr = ipaddr << 8;
    int addr_2 = ipaddr >> 24;
    ipaddr = ipaddr << 8;
    int addr_3 = ipaddr >> 24;
    ipaddr = ipaddr << 8;
    int addr_4 = ipaddr >> 24;
    

    //#简单示例ErrorLogFormat "[%t] [%l] [pid %P] %F: %E: [client %a] %M"
    // %t 当前时间   %l 消息的日志级别   %P 当前进程的进程ID   %F 日志调用的源文件名和行号   %E APR/OS 错误状态代码和字符串
    //[Fri Sep 09 10:42:29.902022 2011] [core:error] [pid 35708:tid 4328636416] [client 72.15.99.187] File does not exist: /usr/local/apache2/htdocs/favicon.ico
    //               [%t]                   [%l]              [pid %P]                    %F:                 %E: [client %a] %M
    /*日志条目中的第一项是消息的日期和时间。
    接下来是生成消息的模块（在本例中为核心）和该消息的严重性级别。
    后面是进程 ID，如果合适，还有遇到该情况的进程的线程 ID。
    接下来，我们有发出请求的客户端地址。
    最后是详细的错误消息，在这种情况下，它表示对不存在的文件的请求*/
    
    if(a==0&&b==1){//第一种错误400
        fp = fopen ("log.txt", "a+");
        fprintf(fp, "%s %s %s %d %s %lu %s %d.%d.%d.%d %s %s\r\n", ctime(&curtime), "[core:error]", "[pid ", process_id, ":tid", tid, "] [client ", addr_1,addr_2,addr_3,addr_4, "] ", "400：Bad Request");
        /*                                                     %s               %s          %s          %d       %s    %lu       %s       %lu       %s                  %s                   
        */
        fclose(fp);
    }
    if(a==0&&b==2){//第二种错误404
        fp = fopen ("log.txt", "a+");
        fprintf(fp, "%s %s %s %d %s %lu %s %d.%d.%d.%d %s %s %s\r\n", ctime(&curtime), "[core:error]", "[pid ", process_id, ":tid", tid, "] [client ", addr_1,addr_2,addr_3,addr_4, "] ", "404：File Not Found File does not exist: ", requestaddr);
        /*                                                     %s               %s          %s          %d       %s    %lu       %s          %lu         %s                         %s                          %s
        */
        fclose(fp);
   }
    if(a==0&&b==3){//第三种错误408
        fp = fopen ("log.txt", "a+");
        fprintf(fp, "%s %s %s %d %s %lu %s %d.%d.%d.%d %s %s\r\n", ctime(&curtime), "[core:error]", "[pid ", process_id, ":tid", tid, "] [client ", addr_1,addr_2,addr_3,addr_4, "] ", "408：Request Time-out");
        /*                                                     %s               %s          %s          %d       %s    %lu       %s        %lu       %s             %s                   
        */
        fclose(fp);
    }
    if(a==0&&b==4){//第四种错误501
        fp = fopen ("log.txt", "a+");
        fprintf(fp, "%s %s %s %d %s %lu %s %d.%d.%d.%d %s %s\r\n", ctime(&curtime), "[core:error]", "[pid ", process_id, ":tid", tid, "] [client ", addr_1,addr_2,addr_3,addr_4, "] ", "501：Not Implemented");
        /*                                                     %s               %s          %s          %d       %s    %lu       %s        %lu       %s             %s                  
        */
        fclose(fp);
    }
    if(a==0&&b==5){//第五种错误505
        fp = fopen ("log.txt", "a+");
        fprintf(fp, "%s %s %s %d %s %lu %s %d.%d.%d.%d %s %s\r\n", ctime(&curtime), "[core:error]", "[pid ", process_id, ":tid", tid, "] [client ", addr_1,addr_2,addr_3,addr_4, "] ", "505：HTTP Version Not Supported");
        /*                                                     %s               %s          %s          %d       %s    %lu       %s        %lu       %s                     %s                   
        */
        fclose(fp);
    }

    //127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
    if(a==1&&b==1){//第一种请求GET
        fp = fopen ("log.txt", "a+");
        fprintf(fp, "%d.%d.%d.%d %s %s %s %s %s %s %d\r\n", addr_1,addr_2,addr_3,addr_4, " - - [", buffer," +0800] \"GET ", requestaddr ," HTTP/1.1", "200", size);
        /*                                         %lu      %s       %s       %s                %s           %s        %s     %d
        */
        fclose(fp);
    }
    if(a==1&&b==2){//第二种请求HEAD
        fp = fopen ("log.txt", "a+");
        fprintf(fp, "%d.%d.%d.%d %s %s %s %s %s %s %d\r\n", addr_1,addr_2,addr_3,addr_4, " - - [", buffer," +0800] \"HEAD ", requestaddr ," HTTP/1.1", "200", size);
        /*                                         %lu     %s         %s       %s                %s           %s        %s     %d
        */
        fclose(fp);
    }
    if(a==1&&b==3){//第三种请求POST
        fp = fopen ("log.txt", "a+");
        fprintf(fp, "%d.%d.%d.%d %s %s %s %s %s %s %d\r\n", addr_1,addr_2,addr_3,addr_4, " - - [", buffer," +0800] \"POST ", requestaddr ," HTTP/1.1", "200", size);
        /*                                         %lu    %s         %s       %s                 %s           %s        %s     %d
        */
        fclose(fp);
    }
}

void GET(char *buf, char *buf_send, Request *request, int client_sock, int sock, unsigned long addr)
{
    struct stat st;
    //implement the GET method
    char *URI = request->http_uri;
    char messagebody[BUF_SIZE];
    char *mimetype;
    if (strcmp(URI, "/") == 0)
    {
        URI = stURI;
    }
    if (stat(URI, &st) == -1)
    {
        perror("stat failed\n");
        strcat(buf_send, "HTTP/1.1 404 Not Found\r\n\r\n");
        my_log(0, 2, addr, request->http_uri, 0);
        return;
    }
    if (S_ISDIR(st.st_mode))
    {
        perror("this is a dir\n");
        strcat(URI, "/index.html");
    }
    if (!exist(URI))
    {
        strcat(buf_send, "HTTP/1.1 404 Not Found\r\n\r\n");
        my_log(0, 2, addr, request->http_uri, 0);
        return;
    }
    else
    {
        int readhetttt = 0;
        int pos = splitext(URI);
        char extension[10];
        //time transfer
        time_t t = st.st_mtime;
        struct tm *mytm = gmtime(&t);
        strftime(timebuffer_modified, sizeof(timebuffer_modified), "%a, %d %b %Y %H:%M:%S GMT\r\n", mytm);
        //time transfer
        //get mimetype
        for (int j = pos + 1; j < strlen(URI); j++)
        {
            extension[j - pos - 1] = URI[j];
        }
        mimetype = hashtext[extension[0]];
        //get mimetype
        int fp = open(URI, O_RDONLY);
        if (fp < 0)
        {
            strcat(buf_send, "HTTP/1.1 501 Not Implemented\r\n\r\n");
            my_log(0, 4, addr, request->http_uri, 0);
            return;
        }
        else
        {
            readhetttt = read(fp, messagebody, BUF_SIZE);
        }
        char length_messagebody[20] = {0};
        Int2String(readhetttt, length_messagebody);
        //get time of my computer
        getmycomputertime();
        //get time of my computer
        strcat(buf_send, "HTTP/1.1 200 OK\r\n");

        strcat(buf_send, "Connection: close\r\n");

        strcat(buf_send, "Content-Type: ");
        strcat(buf_send, mimetype);
        strcat(buf_send, "\r\n");
 
        strcat(buf_send, "Server: liso/1.1\r\n");
 
        strcat(buf_send, "Date: ");
        strcat(buf_send, timebuffer);

        strcat(buf_send, "Content-Length: ");
        strcat(buf_send, length_messagebody);
        strcat(buf_send, "\r\n");

        strcat(buf_send, "Last-Modified: ");
        strcat(buf_send, timebuffer_modified);
        strcat(buf_send, "\r\n");

        strcat(buf_send, messagebody);
        strcat(buf_send, "\r\n");

    }
    memset(messagebody,0,sizeof(messagebody));
}

void HEAD(char *buf, char *buf_send, Request *request, int client_sock, int sock, unsigned long addr)
{
    struct stat st;
    //implement the GET method
    char *URI = request->http_uri;
    char *mimetype;
    if (strcmp(URI, "/") == 0)
    {
        URI = stURI;
    }
    if (stat(URI, &st) == -1)
    {
        perror("stat failed\n");
        strcat(buf_send, "HTTP/1.1 404 Not Found\r\n\r\n");
        my_log(0, 2, addr, request->http_uri, 0);
        return;
    }
    if (S_ISDIR(st.st_mode))
    {
        perror("this is a dir\n");
        strcat(URI, "/index.html");
    }
    if (!exist(URI))
    {
        strcat(buf_send, "HTTP/1.1 404 Not Found\r\n\r\n");
        my_log(0, 2, addr, request->http_uri, 0);
        return;
    }
    else
    {
        int pos = splitext(URI);
        int readhetttt = 0;
        char extension[10];
        //time transfer
        time_t t = st.st_mtime;
        struct tm *mytm = gmtime(&t);
        strftime(timebuffer_modified, sizeof(timebuffer_modified), "%a, %d %b %Y %H:%M:%S GMT\r\n", mytm);
        //time transfer
        //get mimetype
        for (int j = pos + 1; j < strlen(URI); j++)
        {
            extension[j - pos - 1] = URI[j];
        }
        mimetype = hashtext[extension[0]];
        //get mimetype
        int fp = open(URI, O_RDONLY);
        if (fp < 0)
        {
            strcat(buf_send, "HTTP/1.1 501 Not Implemented\r\n\r\n");
            my_log(0, 4, addr, request->http_uri, 0);
            return;
        }
        else
        {
            close(fp);
        }
        int filesize = -1;
        //get time of my computer
        getmycomputertime();
        //get time of my computer
        //get fsize
        struct stat statbuff;
        if (stat(URI, &statbuff) == -1)
        {
            perror("stat failed\n");
            strcat(buf_send, "HTTP/1.1 404 Not Found\r\n\r\n");
            my_log(0, 2, addr, request->http_uri, 0);
            return;
        }
        else
        {
            filesize = statbuff.st_size;
        }
        char length_filesize[20] = {0};
        Int2String(filesize, length_filesize);
        //get fsize
        strcat(buf_send, "HTTP/1.1 200 OK\r\n");
        strcat(buf_send, "Connection: close\r\n");
        strcat(buf_send, "Content-Type: ");
        strcat(buf_send, mimetype);
        strcat(buf_send, "\r\n");
        strcat(buf_send, "Server: liso/1.1\r\n");
        strcat(buf_send, "Date: ");
        strcat(buf_send, timebuffer);
        strcat(buf_send, "Content-Length: ");
        strcat(buf_send, length_filesize);
        strcat(buf_send, "\r\n");
        strcat(buf_send, "Last-Modified: ");
        strcat(buf_send, timebuffer_modified);
        strcat(buf_send, "\r\n");
    }
}

void use_method(char *met, char *buf, char *buf_send, Request *request, int client_sock, int sock, unsigned long addr)
{
    int len_method = strlen(met);
    if (len_method > 4)
    {
        strcat(buf_send, "HTTP/1.1 501 Not Implemented\r\n\r\n");
        my_log(0, 4, addr, request->http_uri, 0);
    }
    
    else if (len_method == 3)
    {
        if (met[0] == _get[0] && met[1] == _get[1] && met[2] == _get[2])
        {
            GET(buf, buf_send, request, client_sock, sock, addr);
            my_log(1, 1, addr, request->http_uri, 0);
        }
        else
        {
            strcat(buf_send, "HTTP/1.1 501 Not Implemented\r\n\r\n");
            my_log(0, 4, addr, request->http_uri, 0);
        }
    }
    
    else if (len_method == 4)
    {
        if (met[0] == _post[0] && met[1] == _post[1] && met[2] == _post[2] && met[3] == _post[3])
        {
            my_log(1, 3, addr, request->http_uri, 0);
            strcat(buf_send, buf);
        }
        else if (met[0] == _head[0] && met[1] == _head[1] && met[2] == _head[2] && met[3] == _head[3])
        {
            
            HEAD(buf, buf_send, request, client_sock, sock, addr);
            my_log(1, 2, addr, request->http_uri, 0);
        }
        else
        {
            strcat(buf_send, "HTTP/1.1 501 Not Implemented\r\n\r\n");
            my_log(0, 4, addr, request->http_uri, 0);
        }
    }
    
    else
    {
        strcat(buf_send, "HTTP/1.1 501 Not Implemented\r\n\r\n");
        my_log(0, 4, addr, request->http_uri, 0);
    }
}

int main(int argc, char* argv[])
{
    int sock, client_sock;
    ssize_t readret;
    socklen_t cli_size;
    struct sockaddr_in addr, cli_addr;
    char buf[BUF_SIZE];
    char buf_send[BUF_SIZE];
    creathash(hashtext);
    int index;


    fprintf(stdout, "----- Liso Server -----\n");
    
    /* all networked programs must create a socket */
    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        fprintf(stderr, "Failed creating socket.\n");
        return EXIT_FAILURE;
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(ECHO_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    /* servers bind sockets to ports---notify the OS they accept connections */
    if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)))
    {
        close_socket(sock);
        fprintf(stderr, "Failed binding socket.\n");
        return EXIT_FAILURE;
    }


    if (listen(sock, 5))
    {
        close_socket(sock);
        fprintf(stderr, "Error listening on socket.\n");
        return EXIT_FAILURE;
    }

    int fdptr = 1024;
    int nready;
    int maxfd = sock;
    int fdclient[1024] = {0};
    fd_set ready_sockets;
    fd_set current_sockets;
    FD_ZERO(&ready_sockets);
    FD_ZERO(&current_sockets);
    FD_SET(sock,&current_sockets);

    /* finally, loop waiting for input and then write it back */
    while (1)
    {
        cli_size = sizeof(cli_addr);

        int num_count = 1;
        ready_sockets = current_sockets;
        nready = select(FD_SETSIZE, &ready_sockets,NULL,NULL,NULL);
        if(nready < 0)
        {
            fprintf(stderr,"Error select.\n");
            return EXIT_FAILURE;
        }

        if(FD_ISSET(sock,&ready_sockets))
        {
            for(fdptr = 0; fdptr < 1024; fdptr++)
            {
               if(fdclient[fdptr] == 0)
               {

                   break;
               }
            }
            fdclient[fdptr] = accept(sock, (struct sockaddr *)&cli_addr,&cli_size);
            if (fdclient[fdptr] == -1)
            {
              close(sock);
              fprintf(stderr, "Error accepting connection.\n");
              return EXIT_FAILURE;
            }
            //start = clock();
            readret = 0;
            fprintf(stdout,"fdclient[i] = %d\n",fdclient[fdptr]);
            FD_SET(fdclient[fdptr], &current_sockets);
            if(fdclient[fdptr]>maxfd)
            {
                maxfd = fdclient[fdptr];
            }
        }


        else
        {
            for(fdptr = 0; fdptr<1024; fdptr++)
            {
                if(FD_ISSET(fdclient[fdptr],&ready_sockets))
                {
                    readret = 0;

                    while((readret = recv(fdclient[fdptr], buf, BUF_SIZE, 0)) >= 1)
                    {
                        
                        int buf_count = 0;
                        char op[2048] = {1};

                        while (strlen(buf) != 0 && buf_count < strlen(buf))
                        {
                            if (buf[buf_count] == '\r' && buf[buf_count + 1] == '\n' && buf[buf_count + 2] == '\r' && buf[buf_count + 3] == '\n')
                            {
                                strncpy(op, buf, buf_count + 4);
                                op[buf_count + 4] = '\0';
                                removeSubString(buf, 0, buf_count + 4);
                                Request *request = parse(op, readret, sock);
                                if (request == NULL)
                                {
                                    strcat(buf_send, "HTTP/1.1 400 Bad request\r\n\r\n");
                                    my_log(0, 1, cli_addr.sin_addr.s_addr, request->http_uri, 0);
                                }

                                else if (!request->judge)
                                {
                                    printf("Http Method %s\n", request->http_method);
                                    printf("Http Version %s\n", request->http_version);
                                    printf("Http Uri %s\n", request->http_uri);
                                    if (!check_http_version(request->http_version))
                                    {
                                        strcat(buf_send, "HTTP/1.1 505 HTTP Version not supported\r\n\r\n");
                                        my_log(0, 5, cli_addr.sin_addr.s_addr, request->http_uri, 0);
                                    }
                                    else
                                    {
                                        use_method(request->http_method, buf, buf_send, request, client_sock, sock, cli_addr.sin_addr.s_addr);
                                    }
                                }            
                        
                                else
                                {
                                    strcat(buf_send, "HTTP/1.1 501 Not Implemented\r\n\r\n");
                                    my_log(0, 4, cli_addr.sin_addr.s_addr, request->http_uri, 0);
                                }
                                buf_count = 0;
                            }
                            buf_count++;
                        }

                        send2client(buf_send,fdclient[fdptr], sock);

                        memset(buf_send,0,sizeof(buf_send));
                        memset(buf, 0, BUF_SIZE);
                        memset(op,0,2048);

                        fprintf(stdout,"client:%d close the link",fdclient[fdptr]);
                        FD_CLR(fdclient[fdptr],&current_sockets);
                        close(fdclient[fdptr]);
                        fdclient[fdptr] = 0;
                        
                        if(readret == 0)
                        {
                            fprintf(stdout,"client:%d close the link",fdclient[fdptr]);
                            FD_CLR(fdclient[fdptr],&current_sockets);
                            close(fdclient[fdptr]);
                            fdclient[fdptr] = 0;
                        }
                        if (readret == -1)
                        {
                            close_socket(fdclient[fdptr]);
                            close_socket(sock);
                            fprintf(stderr, "Error reading from client socket.\n");
                            return EXIT_FAILURE;
                        }

                    }
                }
            }
        }
    }


    close_socket(sock);

    return EXIT_SUCCESS;
}
